﻿using System;
using System.Linq;
using Simp.CodeModel.CoreVisitors;
using System.Collections.Generic;

namespace Simp.CodeModel
{
	/// <summary>Represents a function defined from a name and a composition of other functions.</summary>
	public class CustomFunction : IFunction, IFunctionSerializer
	{
		private readonly IFunction _functionBody;
		private readonly string _functionName;
		private readonly string[] _parameterNames;
		private readonly IFunctionParameterBinding[] _defaultValues;

		/// <summary>Construct a custom function, passing all the necessary data.</summary>
		/// <param name="functionBody"></param>
		/// <param name="functionName"></param>
		/// <param name="parameterNames"></param>
		/// <param name="defaultValues"></param>
		public CustomFunction(
			IFunction functionBody,
			string functionName,
			string[] parameterNames,
			params IFunctionParameterBinding[] defaultValues)
		{
			if (functionBody == null)
			{
				throw new ArgumentNullException("functionBody");
			}
			if (string.IsNullOrEmpty(functionName))
			{
				throw new ArgumentNullException("functionName");
			}
			if (parameterNames == null)
			{
				throw new ArgumentNullException("parameterNames");
			}
			if (defaultValues == null)
			{
				throw new ArgumentNullException("defaultValues");
			}
			if (defaultValues.Length > parameterNames.Length)
			{
				string message = string.Format(
					"There are more default parameters ({0}) than parameters ({1})",
					defaultValues.Length,
					parameterNames.Length);

				throw new ArgumentException(message, "defaultValues");
			}

			_functionBody = functionBody;
			_functionName = functionName;
			_parameterNames = (string[])parameterNames.Clone();
			_defaultValues = (IFunctionParameterBinding[])defaultValues.Clone();
		}

		#region IFunction Members
		string IFunction.FunctionName
		{
			get { return _functionName; }
		}

		string[] IFunction.ParameterNames
		{
			get { return _parameterNames; }
		}

		IFunctionParameterBinding[] IFunction.DefaultParameterValues
		{
			get { return _defaultValues; }
		}

		IFunction IFunction.Simplify(SimplificationMode simplificationMode, params IFunction[] parameters)
		{	//	Replace variable name by values
			FunctionHelper.ValidateParameters(this, parameters);

			IDictionary<string, IFunction> parameterIndex = new Dictionary<string, IFunction>();

			for (int i = 0; i != parameters.Length; ++i)
			{
				parameterIndex.Add(_parameterNames[i], parameters[i]);
			}

			BindFunctionVisitor bindFunctionVisitor = new BindFunctionVisitor(parameterIndex, BindingFailureMode.TolerateFailure);
			IFunction visitedFunction = FunctionHelper.ApplyVisitor(_functionBody, bindFunctionVisitor);
			IFunction simplifiedFunction = FunctionHelper.ApplySimplification(simplificationMode, visitedFunction);

			return simplifiedFunction;
		}

		IFunction IFunction.AcceptVisitor(IFunctionVisitor visitor)
		{
			IFunction visitedFunctionBody = visitor.Visit(_functionBody);
			bool hasDefaultChanged = false;
			IFunctionParameterBinding[] visitedDefaultValues =
				Array.ConvertAll(_defaultValues, delegate(IFunctionParameterBinding binding)
				{
					IFunction visitedFunction = visitor.Visit(binding.Value);

					if (visitedFunction == null)
					{
						return binding;
					}
					else
					{
						hasDefaultChanged = true;

						return FunctionHelper.FunctionParameterBindingFactory.Build(binding.Name, visitedFunction);
					}
				});

			if (hasDefaultChanged && visitedFunctionBody == null)
			{
				return null;
			}
			else
			{
				return new CustomFunction(
					visitedFunctionBody == null ? _functionBody : visitedFunctionBody,
					_functionName,
					_parameterNames,
					visitedDefaultValues);
			}
		}
		#endregion

		#region IFunctionSerializer Members
		void IFunctionSerializer.Serialize(AppendOnlyStringBuilder builder, params IFunction[] parameters)
		{
			IFunction function = this;

			builder.Append(function.FunctionName);
			if (function.ParameterNames.Length > 0)
			{
				builder.Append("(");
				for (int i = 0; i != function.ParameterNames.Length; ++i)
				{
					string parameterName = function.ParameterNames[i];
					IFunction defaultParam = (from binding in function.DefaultParameterValues
											  where binding.Name == parameterName
											  select binding.Value).FirstOrDefault();

					builder.Append(parameterName);
					if (defaultParam != null)
					{
						builder.Append(" = ");
						FunctionHelper.Serialize(defaultParam, builder);
					}
					if (i != function.ParameterNames.Length - 1)
					{
						builder.Append(", ");
					}
				}
				builder.Append(")");
			}
			builder.Append(" = ");
			FunctionHelper.Serialize(_functionBody, builder);
			builder.Append("  ");
		}
		#endregion

		/// <summary>Returns the function body (using the defined parameters).</summary>
		public IFunction FunctionBody
		{
			get { return _functionBody; }
		}
	}
}